classdef dotsTheSwitchboard < dotsAllSingletonObjects
    % @class dotsTheSwitchboard
    % Singleton for central control of object properties
    % @details
    % dotsTheSwitchboard organizes properties on behalf of objects that
    % opt in to share them.  When an object starts sharing one of its
    % properties, dotsTheSwitchbord sets the object's property value to the
    % current shared value.  dotsTheSwitchbord can change the shared value
    % of a property and set its value for all objects that are sharing that
    % property.
    % @details
    % Thus, dotsTheSwitchboard provides central control of properties that
    % multiple objects have in common.  These properties can be user-
    % or program-controled on day to day or moment to moment time scale.
    % For example, dotsTheSwitchboard can coordinate client and server
    % flags, whether or not to play sounds, or the pointer to the current
    % drawing window, across many objects at once.
    % @details
    % For permanent settings that don't change often and dont't require
    % interactive access, see dotsTheMachineConfiguration.
    % dotsTheMachineConfiguration sets some machine-specific defaults for
    % properties shared through dotsTheSwitchboard.
    %
    % @ingroup foundation
    
    properties
        % topsGroupedList of shared properties and values
        % @details
        % sharedProperties has a group with the same name as each property
        % being shared.  Mnemonics in that group are just indexes and the
        % items are handles to objects that have opted in to share the
        % property.
        % @details
        % sharedProperties also has one group names "shared values".
        % Mnemonics in that group are the names of properties being shared
        % and the items are arbitrary values to share for each property.
        sharedProperties;
        
        % struct of default shared property values
        % @details
        % Fields of sharedDefaults match names of properties that objects
        % might need to share.  Each field value is a default value to
        % share for that property.
        % @details
        % Setting sharedDefaults sets the corresponding "shared values" in
        % sharedProperties, even if no objects have opted in for those
        % properties.
        % @details
        % dotsTheMachineConfiguration should provide machine-specific
        % sharedDefaults.
        sharedDefaults;
    end
    
    properties (Hidden)
        % string to use as the "shared values" group of sharedProperties
        sharedValuesGroup = '-shared values-';
    end
    
    methods (Access = private)
        % Constructor is private.
        % @details
        % dotsTheSwitchboard is a singleton object, so its constructor is
        % not accessible.  Use dotsTheSwitchboard.theObject() to access the
        % current instance.
        function self = dotsTheSwitchboard(varargin)
            self.set(varargin{:});
            self.initialize;
        end
    end
    
    methods
        % Restore dotsTheSwitchboard to a fresh state.
        % @details
        % Clears handles and shared values from sharedProperties and sets
        % default shared values from dotsTheMachineConfiguration.
        function initialize(self)
            self.initializeLists({'sharedProperties'});
            dotsTheMachineConfiguration.applySettingsToObject(self, true);
        end
        
        % Copy values in sharedDefaults to "shared values" group.
        function set.sharedDefaults(self, defaults)
            self.sharedDefaults = defaults;
            if ~isempty(defaults) && isstruct(defaults)
                propertyNames = fieldnames(defaults);
                for ii = 1:length(propertyNames)
                    propertyName = propertyNames{ii};
                    propertyDefault = defaults.(propertyName);
                    self.sharedProperties.addItemToGroupWithMnemonic( ...
                        propertyDefault, self.sharedValuesGroup, ...
                        propertyName);
                end
            end
        end
    end
    
    methods (Static)
        % Access the current instance.
        function obj = theObject(varargin)
            persistent self
            if isempty(self) || ~isvalid(self)
                constructor = str2func(mfilename);
                self = feval(constructor, varargin{:});
            else
                self.set(varargin{:});
            end
            obj = self;
        end
        
        % Restore the current instance to a fresh state.
        function reset(varargin)
            factory = str2func([mfilename, '.theObject']);
            self = feval(factory, varargin{:});
            self.initialize;
        end
        
        % Launch a graphical interface to view and edit shared properties.
        function g = gui
            self = dotsTheSwitchboard.theObject;
            
            g = topsGUI;
            listPanel = topsGroupedListPanel(g);
            listPanel.groupString = 'shared properties';
            listPanel.mnemonicString = '';
            listPanel.itemString = 'shared value';
            listPanel.itemDetailPanel.detailsAreEditable = true;
            listPanel.setterFunction = @sharedValueSetter;
            listPanel.populateWithGroupedList(self.sharedProperties);
            g.title = class(self);
            
            function sharedValueSetter(value, list, group, mnemonic, subs)
                self = dotsTheSwitchboard.theObject;
                hasValue = self.sharedProperties.containsMnemonicInGroup( ...
                    mnemonic, self.sharedValuesGroup);
                if hasValue
                    dotsTheSwitchboard.setSharedPropertyValue( ...
                        mnemonic, value);
                    disp(sprintf('set shared "%s" to %s', ...
                        mnemonic, summarizeValue(value)));
                end
            end
        end
        
        % Get the names of shared properties.
        % @details
        % Returns a cell array of strings of the names of shared
        % properties.  Also returns as an optional second output a cell
        % array of corresponding shared values.
        function [propertyNames, propertyValues] = getSharedProperties
            self = dotsTheSwitchboard.theObject;
            [propertyValues, propertyNames] = ...
                self.sharedProperties.getAllItemsFromGroup( ...
                self.sharedValuesGroup);
        end

        % Get the objects that are sharing a given property.
        % @param propertyName the string name of a shared property
        % @details
        % Returns a cell array of objects that are sharing @a propertyName,
        % or {} if no objects are sharing @a propertyName.
        function objects = getObjectsSharingProperty(propertyName)
            self = dotsTheSwitchboard.theObject;
            
            hasObjects = self.sharedProperties.containsGroup(propertyName);
            if hasObjects
                objects = ...
                    self.sharedProperties.getAllItemsFromGroup(propertyName);
            else
                objects = {};
            end
        end
        
        % Set the value for all objects sharing a property.
        % @param propertyName the string name of a shared property
        % @param propertyValue any value to set for all objects that are
        % sharing @a propertyName
        % @details
        % Sets the "shared value" of @a propertyName in sharedProperties to
        % the given @a propertyValue.  If any objects are currently sharing
        % @a propertyName, sets @a propertyValue for each of them.
        function setSharedPropertyValue(propertyName, propertyValue)
            self = dotsTheSwitchboard.theObject;
            
            self.sharedProperties.addItemToGroupWithMnemonic( ...
                propertyValue, self.sharedValuesGroup, propertyName);
            
            objects = ...
                dotsTheSwitchboard.getObjectsSharingProperty(propertyName);
            for ii = 1:length(objects)
                objects{ii}.(propertyName) = propertyValue;
            end
        end
        
        % Get the value for all objects sharing a property.
        % @param propertyName the string name of a shared property
        % @details
        % Returns the "shared value" of @a propertyName stored in
        % sharedProperties.  sharedProperties may contain a value even if
        % no objects are currently sharing @a propertyName.  If
        % sharedProperties contains no value for @a propertyName, returns
        % [].
        function propertyValue = getSharedPropertyValue(propertyName)
            self = dotsTheSwitchboard.theObject;
            
            hasValue = self.sharedProperties.containsMnemonicInGroup( ...
                propertyName, self.sharedValuesGroup);
            if hasValue
                propertyValue = ...
                    self.sharedProperties.getItemFromGroupWithMnemonic( ...
                    self.sharedValuesGroup, propertyName);
            else
                propertyValue = [];
            end
        end
        
        % Opt in to share a property of an object.
        % @param propertyName the string name of a property to start
        % sharing
        % @param object an object that will start sharing the value of its
        % @a propertyName property.
        % @details
        % Adds @a object to sharedProperties, to receive future changes to
        % the shared @a propertyName.  If sharedProperties already contains
        % a "shared value" for @a propertyName, sets that value to @object.
        % @details
        % Returns @object, in case it is a non-handle type.
        function object = sharePropertyOfObject(propertyName, object)
            self = dotsTheSwitchboard.theObject;
            
            alreadySharing = self.sharedProperties.containsItemInGroup( ...
                object, propertyName);
            if ~alreadySharing
                index = self.sharedProperties.length + 1;
                self.sharedProperties.addItemToGroupWithMnemonic( ...
                    object, propertyName, index);
            end
            
            haveSharedValue = ...
                self.sharedProperties.containsMnemonicInGroup( ...
                    propertyName, self.sharedValuesGroup);
            if haveSharedValue
                sharedValue = self.getSharedPropertyValue(propertyName);
                object.(propertyName) = sharedValue;
            else
                sharedValue = object.(propertyName);
                self.setSharedPropertyValue(propertyName, sharedValue);
            end
        end
        
        % Opt in to share many properties of an object.
        % @param propertyNames cell array of string names of properties to
        % start sharing
        % @param object an object that will start sharing the values of its
        % @a propertyNames properties.
        % @details
        % Adds @a object to sharedProperties, to receive future changes to
        % the shared @a propertyNames.  If sharedProperties already
        % contains  "shared value" any of the properties in @a
        % propertyNames, sets the corresponding property value to @object.
        % @details
        % Returns @object, in case it is a non-handle type.
        function object = shareManyPropertiesOfObject(propertyNames, object)
            self = dotsTheSwitchboard.theObject;
            
            for ii = 1:length(propertyNames)
                propertyName = propertyNames{ii};
                object = self.sharePropertyOfObject(propertyName, object);
            end
        end
        
        % Opt out to stop sharing a property of an object.
        % @param propertyName the string name of a property to stop
        % sharing
        % @param object an object that will stop sharing the value of its
        % @a propertyName property.
        % @details
        % Removes @a object from sharedProperties, to ignore future changes
        % to the shared @a propertyName.
        % @details
        % Returns @object, in case it is a non-handle type.
        function object = unsharePropertyOfObject(propertyName, object)
            self = dotsTheSwitchboard.theObject;
            
            alreadySharing = self.sharedProperties.containsItemInGroup( ...
                object, propertyName);
            if alreadySharing
                self.sharedProperties.removeItemFromGroup( ...
                    object, propertyName);
            end
        end
        
        % Opt out to stop sharing many properties of an object.
        % @param propertyNames cell array of string names of properties to
        % stop sharing
        % @param object an object that will stop sharing the values of its
        % @a propertyNames properties.
        % @details
        % Removes @a object from sharedProperties, to ignore future changes
        % to the shared @a propertyNames.
        % @details
        % Returns @object, in case it is a non-handle type.
        function object = unshareManyPropertiesOfObject(propertyNames, object)
            self = dotsTheSwitchboard.theObject;
            
            for ii = 1:length(propertyNames)
                propertyName = propertyNames{ii};
                object = self.unsharePropertyOfObject(propertyName, object);
            end
        end
    end
end